package cn.slipi.core.component.http;

import cn.slipi.common.constants.Constants;
import cn.slipi.common.exception.InternalException;
import cn.slipi.common.rule.custom.entity.MatchCondition;
import cn.slipi.common.utils.TraceUtil;
import cn.slipi.core.plugin.IPluginChain;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SignalType;

import java.util.Optional;

public class WebClientRequest {

    private static final Logger LOGGER = LoggerFactory.getLogger(WebClientRequest.class);

    private static final WebClient WEB_CLIENT = WebClient.create();

    private final ServerWebExchange exchange;

    private final IPluginChain chain;

    private final MatchCondition condition;

    private final String url;

    private final long retry;

    public WebClientRequest(final ServerWebExchange exchange,
                            final IPluginChain chain,
                            final MatchCondition condition,
                            final String url,
                            final Long retry
    ) {
        this.exchange = exchange;
        this.chain = chain;
        this.condition = condition;
        this.url = url;
        this.retry = retry;
    }

    public Mono<Void> invoke() {
        ServerHttpRequest exchangeRequest = exchange.getRequest();
        HttpMethod httpMethod = this.getRealMethod();
        WebClient.RequestBodySpec reqBodySpec =
                WEB_CLIENT.method(httpMethod).uri(url).
                        headers(httpHeaders ->
                        {
                            httpHeaders.addAll(exchangeRequest.getHeaders());
                            httpHeaders.remove(HttpHeaders.HOST);
                        });
        WebClient.RequestHeadersSpec<?> reqHeadersSpec;
        if (requireHttpBody(httpMethod)) {
            reqHeadersSpec = reqBodySpec.contentType(buildMediaType()).body(BodyInserters.fromDataBuffers(exchangeRequest.getBody()));
        } else {
            reqHeadersSpec = reqBodySpec;
        }
        //调用后端服务
        return reqHeadersSpec.exchange().retry(retry).doOnError(this::dealError).flatMap(this::dealResponse).doFinally(this::end);
    }

    private void end(SignalType signalType) {
        TraceUtil.traceEnd();
    }

    /**
     * 获取真实请求方法
     *
     * @return
     */
    private HttpMethod getRealMethod() {
        String requestMethod = condition.getQueryType().toUpperCase();
        if (HttpMethod.GET.matches(requestMethod)) {
            return HttpMethod.GET;
        } else if (HttpMethod.PUT.matches(requestMethod)) {
            return HttpMethod.PUT;
        } else if (HttpMethod.POST.matches(requestMethod)) {
            return HttpMethod.POST;
        } else if (HttpMethod.DELETE.matches(requestMethod)) {
            return HttpMethod.DELETE;
        } else if (HttpMethod.PATCH.matches(requestMethod)) {
            return HttpMethod.PATCH;
        } else if (HttpMethod.TRACE.matches(requestMethod)) {
            return HttpMethod.TRACE;
        } else {
            return HttpMethod.GET;
        }
    }

    private boolean requireHttpBody(HttpMethod method) {
        return HttpMethod.POST == method || HttpMethod.PUT == method || HttpMethod.PATCH == method || HttpMethod.TRACE == method;
    }

    private Mono<Void> dealResponse(ClientResponse clientResponse) {
        LOGGER.debug("-----------请求完成----------");
        HttpStatus httpStatus = clientResponse.statusCode();
        if (httpStatus.is2xxSuccessful()) {
            exchange.getResponse().setStatusCode(HttpStatus.OK);
            exchange.getAttributes().put(Constants.WEBCLIENT_RESPONSE, clientResponse);
        } else if (httpStatus.is5xxServerError()) {
            clientResponse.body((clientHttpResponse, context) -> clientHttpResponse.getBody());
            throw new InternalException(httpStatus.value(), httpStatus.getReasonPhrase());
        } else {
            throw new InternalException(httpStatus.value(), httpStatus.getReasonPhrase());
        }
        return chain.execute(exchange);
    }

    private MediaType buildMediaType() {
        return MediaType.valueOf(Optional.ofNullable(exchange.getRequest().getHeaders().getFirst(HttpHeaders.CONTENT_TYPE)).orElse(MediaType.APPLICATION_JSON_UTF8_VALUE));
    }

    private void dealError(Throwable e) {
        e.printStackTrace();
        throw new InternalException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getLocalizedMessage());
    }
}
